/*
 * Copyright 2001 Alexander Boverman and the original authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <httpd.h>
#include <http_request.h>
#include <util_md5.h>
#include "WtBasicCmds.h"
#include "WtExecute.h"
#include "WtTable.h"
#include "WtTableUtil.h"
#include "WtMultiTable.h"
#include "WtContext.h"
#include "WtUtil.h"
#include "WtContextEvents.h"

/* serverVars command */

int WtServerVarsCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  if (w->web->readEnvStatus == 0) {
    w->web->readEnvStatus = 2;

    ap_add_common_vars(w->web->apReq);
    ap_add_cgi_vars(w->web->apReq);

    if (WtOwnTable(&w->web->serverVars, interp)) {
      if (WtUpdateTableFromApNoMulti(w->web->serverVars,
          w->web->apReq->subprocess_env, interp)) {
        ret = TCL_OK;
        w->web->readEnvStatus = 1;
      }
    }
  } else if (w->web->readEnvStatus == 2) {
    Tcl_AppendResult(interp, "Failed to retrieve server variables.");
  }

  if (w->web->readEnvStatus == 1) {
    WtTableMethodHelper(clientData, interp, objc, objv, &ret,
      &w->web->serverVars, &WtTableType, 0, 1);
  }

  return ret;
}

/* taskSettings command */

int WtTaskSettingsCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  WtTableMethodHelper(clientData, interp, objc, objv, &ret,
    &w->web->taskSettings, &WtTableType, 0, 1);

  return ret;
}

/* requestTime */

int WtRequestTimeCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);
  Tcl_Time *tm = &w->web->requestStartTime;

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, WtToString(objv[0]), NULL);
  } else {
    Tcl_SetObjResult(interp,
      Tcl_NewLongObj((long)(tm->sec * 1000 + tm->usec / 1000)));
    ret = TCL_OK;
  }

  return ret;
}

/* elapsedTime */

int WtElapsedTimeCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);
  Tcl_Time *start, now;
  long duration;

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, WtToString(objv[0]), NULL);
  } else {
    start = &w->web->requestStartTime;
    Tcl_GetTime(&now);
    duration = (now.sec * 1000 + now.usec / 1000) -
      (start->sec * 1000 + start->usec / 1000);
    Tcl_SetObjResult(interp, Tcl_NewLongObj(duration));
    ret = TCL_OK;
  }

  return ret;
}

/* unixRequestTime */

int WtUnixRequestTimeCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, WtToString(objv[0]), NULL);
  } else {
    Tcl_SetObjResult(interp, Tcl_NewLongObj((long)w->apReq->request_time));
    ret = TCL_OK;
  }

  return ret;
}

/* workerInfo command */

int WtWorkerInfoCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w;
  WtWebContext *web;
  Tcl_Obj *tbl;

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, WtToString(objv[0]), NULL);
  } else {
    w = WtGetAssocContext(interp);
    web = w->web;
    tbl = WtNewTableObj();
    WtTableSetStrToBool(tbl, "isPersistent", web->isPersistent);
    WtTableSetStrToInt(tbl, "requestCount", w->requestCount);
    WtTableSetStrToInt(tbl, "ctxRequestCount", web->ctxRequestCount);
    WtTableSetStrToInt(tbl, "interpRequestCount", web->interpRequestCount);
    WtTableSetStrToLong(tbl, "interpCreationTime",
      (long)web->interpCreationTime);
    WtTableSetStrToBool(tbl, "packageLoadDone", web->packageLoadDone);
    WtTableSetStrToInt(tbl, "pid", getpid());
    WtTableSetStrToInt(tbl, "tid", gettid());
    Tcl_SetObjResult(interp, tbl);
    ret = TCL_OK;
  }

  return ret;
}

/* taskInfo command */

int WtTaskInfoCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w;
  WtWebContext *web;
  Tcl_Obj *tbl;
  const char *hs;

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, WtToString(objv[0]), NULL);
  } else {
    w = WtGetAssocContext(interp);
    web = w->web;
    tbl = WtNewTableObj();
    WtTableSetStrToBool(tbl, "errorState", web->errorState);
    WtTableSetStrToObj(tbl, "taskNamespace", web->taskNamespace);
    WtTableSetStrToBool(tbl, "importCommands", web->importCommands);
    WtTableSetStrToBool(tbl, "readEnvStatus", web->readEnvStatus);
    WtTableSetStrToInt(tbl, "parseStatus", web->clientRequest.parseStatus);
    WtTableSetStrToInt(tbl, "readRequestStatus", web->clientRequest.readRequestStatus);
    WtTableSetStrToInt(tbl, "readCookiesStatus", web->clientRequest.readCookiesStatus);

    if (web->clientResponse.headersStatus == WT_STATUS_NONE) {
      hs = "none";
    } else if (web->clientResponse.headersStatus == WT_STATUS_OK) {
      hs = "sent";
    } else if (web->clientResponse.headersStatus == WT_STATUS_ERROR) {
      hs = "error";
    }
    WtTableSetStrToStr(tbl, "headersStatus", hs);

    WtTableSetStrToInt(tbl, "bytesSent", w->apReq->bytes_sent);
    WtTableSetStrToBool(tbl, "responseCode", web->clientResponse.status);
    WtTableSetStrToBool(tbl, "deleteInterp", web->deleteInterp);

    Tcl_SetObjResult(interp, tbl);
    ret = TCL_OK;
  }

  return ret;
}

/* wtVersion command */

int WtVersionCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      Tcl_GetString(objv[0]), NULL);
  } else {
    Tcl_SetResult(interp, (char *)wtVersion, NULL);
    ret = TCL_OK;
  }

  return ret;
}

/* virtual command */

int WtVirtualCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);
  request_rec *rr = NULL;
  const char *uri;
  Tcl_Obj *statusObj;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      Tcl_GetString(objv[0]), " ?path?", NULL);
  } else {
    uri = Tcl_GetString(objv[1]);
    rr = ap_sub_req_lookup_uri(uri, w->web->apReq);
    if (!rr) {
      Tcl_AppendResult(interp, "Failed to initialize URI \"",
        uri, "\".");
    } else if (rr->status != 200) {
      statusObj = Tcl_NewIntObj(rr->status);
      Tcl_IncrRefCount(statusObj);
      Tcl_AppendResult(interp, "Failed to find URI \"", uri, "\": Status code: ",
        Tcl_GetString(statusObj), ".");          
      Tcl_DecrRefCount(statusObj);
    } else if (ap_run_sub_req(rr)) {
      statusObj = Tcl_NewIntObj(rr->status);
      Tcl_IncrRefCount(statusObj);
      Tcl_AppendResult(interp, "Failed to run URI \"", uri, "\": Status code: ",
        Tcl_GetString(statusObj), ".");
      Tcl_DecrRefCount(statusObj);
    } else {
      ret = TCL_OK;
    }
  }

  if (rr) {
    ap_destroy_sub_req(rr);
  }

  return ret;
}

/* taskNotices command */

int WtTaskNoticesCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR;
  WtContext *w = WtGetAssocContext(interp);

  if (objc != 1) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, WtToString(objv[0]), NULL);
  } else {
    Tcl_SetObjResult(interp, w->web->notices);
    ret = TCL_OK;
  }

  return ret;
}

/* serverLog command */

int WtParseLogLevel(const char *levelStr)
{
  int level = -1;

  if (!strcmp(levelStr, "emerg")) {
    level = APLOG_EMERG;
  } else if (!strcmp(levelStr, "alert")) {
    level = APLOG_ALERT;
  } else if (!strcmp(levelStr, "crit")) {
    level = APLOG_CRIT;
  } else if (!strcmp(levelStr, "error")) {
    level = APLOG_ERR;
  } else if (!strcmp(levelStr, "warning")) {
    level = APLOG_WARNING;
  } else if (!strcmp(levelStr, "notice")) {
    level = APLOG_NOTICE;
  } else if (!strcmp(levelStr, "info")) {
    level = APLOG_INFO;
  } else if (!strcmp(levelStr, "debug")) {
    level = APLOG_DEBUG;
  }

  return level;
}

char *WtLogLevelString(int level)
{
  char *str = NULL;

  switch (level) {
    case APLOG_EMERG:
      str = "emerg";
      break;
    case APLOG_ALERT:
      str = "alert";
      break;
    case APLOG_CRIT:
      str = "crit";
      break;
    case APLOG_ERR:
      str = "error";
      break;
    case APLOG_WARNING:
      str = "warning";
      break;
    case APLOG_NOTICE:
      str = "notice";
      break;
    case APLOG_INFO:
      str = "info";
      break;
    case APLOG_DEBUG:
      str = "debug";
      break;
  }

  return str;
}

int WtServerLogCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, level, usage = 0;
  WtContext *w = WtGetAssocContext(interp);
  char *path;

  if (objc < 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix,
      NULL);
    usage = 1;
  } else {
    path = w->web->apReq->filename ? w->web->apReq->filename : "";

    if (objc == 2) {
      WtLog(path, -1, APLOG_INFO | APLOG_NOERRNO, w,
        "Wtcl: %s", WtToString(objv[1]));
      ret = TCL_OK;
    } else if (objc == 3) {
      level = WtParseLogLevel(WtToString(objv[1]));
      if (level == -1) {
        Tcl_AppendResult(interp, "Unknown log level specified. Usage: ", NULL);
        usage = 1;
      } else {
        WtLog(path, -1, level, w,
          "Wtcl: %s", WtToString(objv[2]));
        ret = TCL_OK;
      }
    }
  }

  if (usage) {
    Tcl_AppendResult(interp, WtToString(objv[0]),
      " [emerg | alert | crit | error | warning | notice | info | ",
      "debug | none] message", NULL);
  }

  return ret;
}

int WtLogLevelCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, code, usage = 0;
  char *name;
  WtContext *w = WtGetAssocContext(interp);
  char *path;

  if (objc < 2) {
    usage = 1;
  } else if (!strcmp(WtToString(objv[1]), "code")) {
    if (objc < 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " name", NULL);
    } else {
      code = WtParseLogLevel(WtToString(objv[2]));
      if (code == -1) {
        Tcl_AppendResult(interp, "Unknown log level name: \"",
          WtToString(objv[2]), "\".", NULL);
      } else {
        Tcl_SetObjResult(interp, Tcl_NewIntObj(code));
        ret = TCL_OK;
      }
    }
  } else if (!strcmp(WtToString(objv[1]), "name")) {
    if (objc < 3) {
      Tcl_AppendResult(interp, wtBadUsagePrefix,
        WtToString(objv[0]), " ", WtToString(objv[1]),
        " code", NULL);
    } else if (Tcl_GetIntFromObj(interp, objv[2], &code)) {
      name = WtLogLevelString(code);
      if (!name) {
        Tcl_AppendResult(interp, "Unknown log level code: ",
          WtToString(objv[2]), ".", NULL);
      } else {
        Tcl_SetObjResult(interp, WtNewString(name));
        ret = TCL_OK;
      }
    }
  } else {
    usage = 1;
  }

  if (usage) {
    Tcl_AppendResult(interp, wtBadUsagePrefix2,
      WtToString(objv[0]), " name code\n",
      WtToString(objv[0]), " code name\n",
      NULL);
  }

  return ret;
}

/* sleep command */

int WtSleepCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj *const objv[])
{
  int ret = TCL_ERROR, msec;

  if (objc != 2) {
    Tcl_AppendResult(interp, wtBadUsagePrefix, " msec", NULL);
  } else if (Tcl_GetIntFromObj(interp, objv[1], &msec) == TCL_OK) {
    Tcl_Sleep(msec);
    ret = TCL_OK;
  }

  return ret;
}

/* Initialize commands. */

void WtInitServerCommands(Tcl_Interp *interp)
{
  WtContext *w = WtGetAssocContext(interp);

  WtLog(HERE, APLOG_DEBUG | APLOG_NOERRNO, w,
    "Wtcl: WtInitServerCommands.");

  Tcl_CreateObjCommand(interp, "::wt::server::serverVars",
    WtServerVarsCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::taskSettings",
    WtTaskSettingsCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::wtVersion",
    WtVersionCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::virtual",
    WtVirtualCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::requestTime",
    WtRequestTimeCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::elapsedTime",
    WtElapsedTimeCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::unixRequestTime",
    WtUnixRequestTimeCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::workerInfo",
    WtWorkerInfoCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::taskInfo",
    WtTaskInfoCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::taskNotices",
    WtTaskNoticesCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::serverLog",
    WtServerLogCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::logLevel",
    WtLogLevelCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::sleep",
    WtSleepCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::events",
    WtContextEventsCmd, NULL, NULL);

  Tcl_CreateObjCommand(interp, "::wt::server::atEnd",
    WtAtEndCmd, NULL, NULL);
}
